package com.google.code.mochaccino.framework.xml;
/*
 * Copyright 2012 Claude Houle claude.houle@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import com.google.common.base.Preconditions;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

/**
 * A collection of utilities to transform primitives (or ojects) from/to
 * their string reprsentation. Useful in the context of the XML Streaming (or DOM manipulation).
 */
public final class TranscodingUtil {

	private TranscodingUtil() {
	}

	/** Decode */
	public static <T> T decode( String value, Class<T> clazz ) {
		return decode( value, clazz, null );
	}

	/** Decode */
	@SuppressWarnings( "unchecked" )
	public static <T> T decode( String value, Class<T> clazz, T defaultValue ) {
		if ( value == null ) {
			return defaultValue;
		}

		Preconditions.checkNotNull( clazz, "Class cannot be null" );
		Preconditions.checkArgument( isClassSupported( clazz ), "Unsupported class: " + clazz.getName() );

		try {
			if ( clazz.equals( Double.class ) ) {
				return (T) decodeAsDouble( value );
			}
			if ( clazz.equals( Float.class ) ) {
				return (T) decodeAsFloat( value );
			}
			if ( clazz.equals( Integer.class ) ) {
				return (T) decodeAsInteger( value );
			}
			if ( clazz.equals( Long.class ) ) {
				return (T) decodeAsLong( value );
			}
			if ( clazz.equals( Short.class ) ) {
				return (T) decodeAsShort( value );
			}
			if ( clazz.equals( Byte.class ) ) {
				return (T) decodeAsByte( value );
			}
			if ( clazz.equals( Boolean.class ) ) {
				return (T) decodeAsBoolean( value );
			}
			if ( clazz.equals( Date.class ) ) {
				return (T) decodeAsDateTime( value );
			}
			if ( clazz.equals( GregorianCalendar.class ) ) {
				return (T) decodeAsCalendar( value );
			}
			if ( clazz.equals( byte[].class ) ) {
				return (T) decodeAsBytes( value );
			}
			if ( clazz.equals( Character.class ) ) {
				return (T) decodeAsCharacter( value );
			}
			if ( clazz.equals( BigDecimal.class ) ) {
				return (T) decodeAsBigDecimal( value );
			}
			if ( clazz.equals( BigInteger.class ) ) {
				return (T) decodeAsBigInteger( value );
			}
			return defaultValue;
		} catch ( Throwable e ) {
			return defaultValue;
		}
	}

	/** decode */
	public static BigDecimal decodeAsBigDecimal( String value ) {
		return new BigDecimal( value );
	}

	/** decode */
	public static BigInteger decodeAsBigInteger( String value ) {
		return new BigInteger( value );
	}

	/** decode */
	public static Boolean decodeAsBoolean( String value ) {
		return Boolean.valueOf( value ).booleanValue();
	}

	/** decode */
	public static Byte decodeAsByte( String value ) {
		return Byte.valueOf( value ).byteValue();
	}

	/** Decode As Blob */
	public static byte[] decodeAsBytes( String value ) {
		BASE64Decoder coder = new BASE64Decoder();
		try {
			return coder.decodeBuffer( value );
		} catch ( IOException e ) {
			return null;
		}
	}

	/** decode */
	public static GregorianCalendar decodeAsCalendar( String value ) throws ParseException {
		GregorianCalendar cal = new GregorianCalendar();
		cal.setTime( decodeAsDateTime( value ) );
		return cal;
	}

	/** decode */
	public static Character decodeAsCharacter( String value ) {
		return value.charAt( 0 );
	}

	/** decode */
	public static Double decodeAsDouble( String value ) {
		return Double.valueOf( value ).doubleValue();
	}

	/** decode */
	public static Float decodeAsFloat( String value ) {
		return Float.valueOf( value ).floatValue();
	}

	/** decode */
	public static Integer decodeAsInteger( String value ) {
		return Integer.parseInt( value );
	}

	/** decode */
	public static Long decodeAsLong( String value ) {
		return Long.parseLong( value );
	}

	/** decode */
	public static Short decodeAsShort( String value ) {
		return Short.valueOf( value ).shortValue();
	}

	/** decode */
	public static Date decodeAsDateTime( String value ) throws ParseException {
		SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd\'T\'HH:mm:ss.SSSSZ" );
		int idx = value.lastIndexOf( ':' );
		StringBuilder buf = new StringBuilder();
		buf.append( value.substring( 0, idx ) );
		buf.append( value.substring( idx + 1 ).replaceAll( ":", "" ) );
		return format.parse( buf.toString() );
	}

	/** Encode As Blob */
	public static String encode( byte[] value ) {
		if ( value == null ) {
			return "";
		}
		BASE64Encoder coder = new BASE64Encoder();
		return coder.encode( value );
	}

	/** encode */
	public static String encode( Calendar value ) {
		if ( value == null ) {
			return "";
		}
		SimpleDateFormat format = new SimpleDateFormat( "yyyy-MM-dd\'T\'HH:mm:ss.SSSS" );
		String encodedValue = format.format( value.getTime() );
		int tzOffset = value.get( Calendar.ZONE_OFFSET );
		if ( value.getTimeZone().inDaylightTime( value.getTime() ) ) {
			tzOffset += value.get( Calendar.DST_OFFSET );
		}
		encodedValue += encodeTimeZoneAsXML( tzOffset );
		return encodedValue;
	}

	/** encode */
	private static String encodeTimeZoneAsXML( int tzOffset ) {
		char[] buf = new char[6];
		int idx = 0;
		if ( tzOffset >= 0 ) {
			buf[idx++] = '+';
		} else {
			buf[idx++] = '-';
			tzOffset = -tzOffset;
		}
		tzOffset /= 60 * 1000;
		idx += writeIntToCharArray( tzOffset / 60, 2, buf, idx );
		buf[idx++] = ':';
		idx += writeIntToCharArray( tzOffset % 60, 2, buf, idx );
		return new String( buf, 0, idx );
	}

	/** Write Int to Char Array */
	private static int writeIntToCharArray( int value, int minDigits, char[] array, int index ) {
		boolean isNegative = value < 0;
		if ( isNegative ) {
			value = -value;
		}
		int digits = 0;
		int maxIndex = index + minDigits;
		for ( int i = index; (i < maxIndex) || (value != 0); i++ ) {
			array[i] = (char) ('0' + (short) (value % 10));
			value /= 10;
			digits++;
		}
		if ( isNegative ) {
			array[index + digits++] = '-';
		}
		// -- reverse order
		maxIndex = (index << 1) + digits - 1;
		for ( int i = index + (digits - 1 >> 1); i >= index; --i ) {
			char swap = array[i];
			array[i] = array[maxIndex - i];
			array[maxIndex - i] = swap;
		}
		return digits;
	}

	/** encode */
	public static String encode( Date value ) {
		if ( value == null ) {
			return "";
		}
		Calendar wCal = new GregorianCalendar();
		wCal.setTime( value );
		return encode( wCal );
	}

	/** Encode Value */
	public static String encode( Object value ) {
		if ( value == null ) {
			return "";
		}
		if ( !isClassSupported( value.getClass() ) ) {
			throw new IllegalArgumentException( "Unsupported class: " + value.getClass().getName() );
		}
		if ( value.getClass().equals( Boolean.class ) ) {
			return encode( ((Boolean) value).booleanValue() );
		} else if ( value.getClass().equals( Integer.class ) ) {
			return encode( ((Number) value).intValue() );
		} else if ( value.getClass().equals( Double.class ) ) {
			return encode( ((Number) value).doubleValue() );
		} else if ( value.getClass().equals( Float.class ) ) {
			return encode( ((Number) value).floatValue() );
		} else if ( value.getClass().equals( Byte.class ) ) {
			return encode( ((Number) value).byteValue() );
		} else if ( value.getClass().equals( Short.class ) ) {
			return encode( ((Number) value).shortValue() );
		} else if ( value.getClass().equals( Long.class ) ) {
			return encode( ((Number) value).longValue() );
		} else if ( value.getClass().equals( Character.class ) ) {
			return encode( ((Character) value).charValue() );
		} else if ( value.getClass().equals( String.class ) ) {
			return encode( ((String) value).toCharArray() );
		} else if ( value.getClass().equals( BigInteger.class ) ) {
			return encode( ((BigInteger) value) );
		} else if ( value.getClass().equals( BigDecimal.class ) ) {
			return encode( ((BigDecimal) value) );
		} else if ( value.getClass().equals( Calendar.class ) ) {
			return encode( ((Calendar) value) );
		} else if ( value.getClass().equals( Date.class ) ) {
			return encode( ((Date) value) );
		} else if ( value.getClass().equals( byte[].class ) ) {
			return encode( ((Date) value) );
		} else if ( value.getClass().isEnum() ) {
			Enum<?> enumeration = (Enum<?>) value;
			return enumeration.name();
		}
		return "";
	}

	/** encode */
	public static String encode( BigDecimal value ) {
		if ( value == null ) {
			return "";
		}
		return String.valueOf( value );
	}

	/** encode */
	public static String encode( BigInteger value ) {
		if ( value == null ) {
			return "";
		}
		return String.valueOf( value );
	}

	/** encode */
	public static String encode( boolean value ) {
		return String.valueOf( value );
	}

	/** encode */
	public static String encode( byte value ) {
		return String.valueOf( value );
	}

	/** encode */
	public static String encode( char value ) {
		return String.valueOf( value );
	}

	/** encode */
	public static String encode( char[] value ) {
		return String.valueOf( value );
	}

	/** encode */
	public static String encode( double value ) {
		return String.valueOf( value );
	}

	/** encode */
	public static String encode( float value ) {
		return String.valueOf( value );
	}

	/** encode */
	public static String encode( int value ) {
		return String.valueOf( value );
	}

	/** encode */
	public static String encode( long value ) {
		return String.valueOf( value );
	}

	/** encode */
	public static String encode( short value ) {
		return Short.toString( value );
	}

	public static boolean isClassSupported( Class<?> clazz ) {
		if ( clazz == null ) {
			return false;
		}
		if ( clazz.equals( Double.class ) ) {
			return true;
		}
		if ( clazz.equals( Float.class ) ) {
			return true;
		}
		if ( clazz.equals( Integer.class ) ) {
			return true;
		}
		if ( clazz.equals( Long.class ) ) {
			return true;
		}
		if ( clazz.equals( Short.class ) ) {
			return true;
		}
		if ( clazz.equals( Byte.class ) ) {
			return true;
		}
		if ( clazz.equals( Boolean.class ) ) {
			return true;
		}
		if ( clazz.equals( Date.class ) ) {
			return true;
		}
		if ( clazz.equals( GregorianCalendar.class ) ) {
			return true;
		}
		if ( clazz.equals( byte[].class ) ) {
			return true;
		}
		if ( clazz.equals( Character.class ) ) {
			return true;
		}
		if ( clazz.equals( BigDecimal.class ) ) {
			return true;
		}
		if ( clazz.equals( BigInteger.class ) ) {
			return true;
		}
		return false;
	}
}
